/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package opk.Solver;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import opk.Generator.Edge;
import opk.Generator.MyLogger;
import opk.Generator.Shape;
import opk.Graphics.Visio;

/**
 *
 * @author Dawid
 */
public class OptimalAlgorithm implements Runnable
{
    private ArrayList<Shape> allShapes;
    private ArrayList<Shape> includedShapes;
    private ArrayList<Shape> notIncludedShapes;
    private ArrayList<Shape> extraIncludedShapes;
    private ResultShape currentBestResult;
    private ResultShape currentResult;
    private Visio visio;
    private int counter;
    private int counter2;
    private static boolean stop;
    private MyLogger loger;
    private HashMap<Shape, Integer> shapeNumber;
    private int shapeBoundNumber;
    private HashMap<GraphPoint, HashMap<GraphPoint, ArrayList<Edge>>> shortcuts;
    private boolean useSorting;
    private int withBound = 0;
    JLabel label;
    private ResultShape tempResult;
    private int time;
    private Date start;

    public OptimalAlgorithm(Visio visio, boolean useSorting, MyLogger loger, JLabel label)
    {
        this.loger = loger;
        this.visio = visio;
        this.useSorting = useSorting;
        this.currentBestResult = new ResultShape();
        this.label = label;
        
    }

    public OptimalAlgorithm(Visio visio, boolean useSorting, MyLogger loger, ResultShape bound, JLabel label)
    {
        this.loger = loger;
        this.visio = visio;
        this.useSorting = useSorting;
        this.currentBestResult = new ResultShape();
        withBound = 2;
        if(bound!=null)
            this.currentBestResult = bound;
        else
            this.currentBestResult = new ResultShape();
        this.label = label;
    }

    


    public void loadAlgorithm()
    {
        start = new Date();

        this.shapeBoundNumber = 0;
        this.shapeNumber = new HashMap<Shape, Integer>();
        OptimalAlgorithm.stop = false;
        this.allShapes = new ArrayList<Shape>(visio.getList().size());
        this.notIncludedShapes = new ArrayList<Shape>(visio.getList().size());
        this.includedShapes = new ArrayList<Shape>(visio.getList().size() - 1);
        this.extraIncludedShapes = new ArrayList<Shape>(visio.getList().size() - 1);
        this.currentResult = new ResultShape();
        for (Shape shape : visio.getList())
        {
            shape.countAndSetGeometricCenter();
            this.allShapes.add(new Shape(shape));
            this.notIncludedShapes.add(new Shape(shape));
        }

        DijkstraAlgorithm dijkstraAlgorithm = new DijkstraAlgorithm();
        this.shortcuts = dijkstraAlgorithm.getAllShortcuts(allShapes, false);
     //   System.out.println("Dikstra done!");
        this.counter = 0;
        this.counter2 = 0;
    }

    public Shape getOptimalResult()
    {
        Shape fakeShape = new Shape();
        fakeShape.addNode(0, 0);
        fakeShape.countAndSetGeometricCenter();
        ArrayList<Shape> notIncludedShapesLocal = new ArrayList<Shape>(notIncludedShapes);
       // if (useSorting)
        //{
            Collections.sort(notIncludedShapesLocal, new MinDistanceComparator(fakeShape));
            Collections.sort(allShapes, new MinDistanceComparator(fakeShape));
       // }

        for (int i = 0; i < notIncludedShapes.size(); ++i)
        {
            shapeNumber.put(notIncludedShapes.get(i), i);
        }

        for (Shape shape : notIncludedShapesLocal)
        {
            if (stop)
            {
                return currentBestResult;
            }
            updateCurrentResult(shape);
            ++shapeBoundNumber;
            addNextShape();
            revertAddingShape(shape);
            currentResult.clear();
        }

//        loger.logIn(withBound, tempResult);
        loger.logIn(withBound, currentBestResult, tempResult);
     //   System.out.println("Done");
        return currentBestResult;
    }

    private void addNextShape()
    {
        Date now = new Date();
        time = (int) ((now.getTime() - start.getTime()) / 1000);
        if(tempResult==null && time>3)
        {
            tempResult = new ResultShape(currentBestResult.nodes);
            tempResult.setNumberOfShapes(currentBestResult.getNumberOfShapes());
            tempResult.setRatio(currentBestResult.getRatio());
        }

        ++counter;

        ArrayList<Shape> notIncludedShapesLocal = new ArrayList<Shape>(notIncludedShapes);

        if (useSorting && includedShapes.size() == 1) Collections.sort(notIncludedShapesLocal, new MinDistanceComparator(includedShapes.get(0)));
        ResultShape resultShapeCopy = new ResultShape(currentResult.getShapePoints());          //copy, instead of adding and removing
        for (Shape shape : notIncludedShapesLocal)
        {
            if (stop || notIncludedShapesLocal.size() == 1)
            {
                return;
            }
            if (shapeNumber.get(shape) < shapeBoundNumber)
            {
      //            continue;
            }
            updateCurrentResult(shape);



            if (checkBound())
            {
                ++counter2;
                revertAddingShape(shape);
                currentResult = resultShapeCopy;
                continue;
            }

            if (currentResult.getRatio() > currentBestResult.getRatio())
            {
                updateBestResult();
            }
            ++shapeBoundNumber;
            addNextShape();                    //recurency
            --shapeBoundNumber;

            revertAddingShape(shape);
            currentResult = resultShapeCopy;                                                    //getting result shape without the newest shape
        }
    }

    private void revertAddingShape(Shape shape)
    {
        includedShapes.remove(shape);
        notIncludedShapes.add(shape);
        if (!extraIncludedShapes.isEmpty())
        {
            includedShapes.removeAll(extraIncludedShapes);
            notIncludedShapes.addAll(extraIncludedShapes);
            extraIncludedShapes.clear();
        }
    }

    private boolean checkBound()        //returns true if bound result will be better in all cases
    {
        return ((double) allShapes.size() - 1) / currentResult.getPerimeter() < currentBestResult.getRatio();
    }

    private void updateCurrentResult(Shape shape)
    {
        includedShapes.add(shape);
        notIncludedShapes.remove(shape);
        ArrayList<Shape> shapesToMerge = new ArrayList<Shape>(2);
        shapesToMerge.add(shape);
        if (!currentResult.nodes.isEmpty())
        {
            shapesToMerge.add(currentResult);
        }

        currentResult = (new GrahamAlgorithm()).getResultShapeAfterGraham(shapesToMerge);
  //      updateIncludedShapes();   //potrzebne?
        correctCurrentResult();
        updateIncludedShapes();

        currentResult.setRatio((double) currentResult.getNumberOfShapes() / (double) currentResult.getPerimeter());
    }

    private void updateIncludedShapes()
    {
        currentResult.setNumberOfShapes(includedShapes.size());

        Iterator<Shape> shapeIt = notIncludedShapes.iterator();
        while (shapeIt.hasNext())
        {
            Shape currentShapeToCheck = shapeIt.next();
            boolean isInside = true;
            for (Point point : currentShapeToCheck.nodes)
            {
                if (!currentResult.isPointInside(point))
                {
                    isInside = false;
                    break;
                }
            }
            if (isInside)
            {
                currentResult.setNumberOfShapes(currentResult.getNumberOfShapes() + 1);
                includedShapes.add(currentShapeToCheck);
                extraIncludedShapes.add(currentShapeToCheck);
                shapeIt.remove();
            }
        }
    }

    private void correctCurrentResult()
    {
        currentResult.makeEdgesList();
        ArrayList<Edge> edges = currentResult.getEdgesList();

        for (int i = 0; i < edges.size(); ++i)
        {
            if (edges.get(i).intersectsWithAnything(allShapes))
            {
                GraphPoint graphPoint1 = edges.get(i).getGraphPoint1();
                GraphPoint graphPoint2 = edges.get(i).getGraphPoint2();

                HashMap<GraphPoint, ArrayList<Edge>> firstPointShortCuts = shortcuts.get(graphPoint1);
                ArrayList<Edge> edgesToAdd = firstPointShortCuts.get(graphPoint2);

                if (edgesToAdd == null)
                {
                    System.out.println("");
                }
                edges.addAll(i, edgesToAdd);
                i += edgesToAdd.size();
                edges.remove(i);
                --i;
            }
        }

        currentResult.setEdgesList(edges);
    }

    private void updateBestResult()
    {
        if (notIncludedShapes.isEmpty())
        {
            return;
        }
        currentBestResult = currentResult;
        visio.setResult(currentBestResult);

        SwingUtilities.invokeLater(visio);
    }

    public void run()
    {

        loadAlgorithm();

        //System.out.println("loaded");
        getOptimalResult();
        System.out.println("DONE");

        label.setText("gotowe");
    }

    public void stopAlgorithm()
    {
        OptimalAlgorithm.stop = true;
    }

    private boolean allIncludedAreInside(ArrayList<Edge> edges)
    {
        ResultShape currentResultTemp = new ResultShape(ResultShape.edgesToPoints(edges));
        for (Shape shape : includedShapes)
        {
            shape.countAndSetGeometricCenter();
            if (!currentResultTemp.isPointInside(shape.getGeometricCenter()))
            {
                return false;
            }
        }

        return true;

    }
}
